package com.igoal.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.appfuse.model.BaseObject;
import org.appfuse.model.LabelValue;
import org.compass.annotations.Searchable;
import org.compass.annotations.SearchableId;
import org.compass.annotations.SearchableProperty;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.Type;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.userdetails.UserDetails;

import com.igoal.campaign.xmas2010.entity.Xmas2010;

@Entity
@Table(name = "user")
@Searchable
public class User extends BaseObject implements Serializable, UserDetails,
		Audit {
	private static final long serialVersionUID = 3832626162173359411L;

	private Long id;
	private String username; // required
	private String password; // required
	private String confirmPassword;
	private String passwordHint;
	private String email; // required; unique
	private Date createDate;
	private Date updateDate;
	private String activationCode;
	private String avatar;
	private Integer numberFriends;
	private Integer numberGoals;
	private Integer numberFollowed;
	private Integer numberFollows;
	private boolean enabled;
	private Integer version;
	private boolean accountExpired;
	private boolean accountLocked;
	private boolean credentialsExpired;
	private int day;
	private int month;
	private int year;
	private Integer subscriptionType;
	private boolean receiveSubscription;

	private Set<UserStatus> userStatuses = new HashSet<UserStatus>(0);
	private Set<UserThought> wallPosts = new HashSet<UserThought>(0);
	private Set<UserThought> postsOnWall = new HashSet<UserThought>(0);
	private Set<InfoQueueEntry> invites = new HashSet<InfoQueueEntry>(0);
	private Set<UserActivity> notificationLogs = new HashSet<UserActivity>(0);
	private Set<UserActivity> actionLogs = new HashSet<UserActivity>(0);
	private Set<Xmas2010> xmasWishes = new HashSet<Xmas2010>(0);

	private UserInfo userInfo;

	private Set<Role> roles = null;
	private Set<Goal> goals = null;
	private Set<Category> categories = null;
	
	private Set<UserFriend> friends = new HashSet<UserFriend>(0);
	private Set<UserFriend> friendOf = new HashSet<UserFriend>(0);
	private Set<UserGoal> userGoals = new HashSet<UserGoal>(0);
	private Set<Message> receivedMessages = new HashSet<Message>(0);
	private Set<Message> sentMessages = new HashSet<Message>(0);
	private Set<Hobby> hobbies = new HashSet<Hobby>(0);
	private Set<UserCategory> userCategories = new HashSet<UserCategory>();
	private Set<UserJob> userJobs = new HashSet<UserJob>(0);
	private Set<UserSchool> userSchools = new HashSet<UserSchool>(0);
	private Set<UserRole> userRoles = new HashSet<UserRole>(0);
	private Set<UserCompany> userCompanies = new HashSet<UserCompany>(0);

	/**
	 * Default constructor - creates a new instance with no values set.
	 */
	public User() {
	}

	/**
	 * Create a new instance and set the username.
	 * 
	 * @param username
	 *            login name for user.
	 */
	public User(final String username) {
		this.username = username;
	}

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@SearchableId
	public Long getId() {
		return id;
	}

	@Column(nullable = false, length = 50, unique = true)
	@SearchableProperty
	public String getUsername() {
		return username;
	}

	@Column(nullable = false)
	public String getPassword() {
		return password;
	}

	@Transient
	public String getConfirmPassword() {
		return confirmPassword;
	}

	@Column(name = "password_hint")
	public String getPasswordHint() {
		return passwordHint;
	}

	@Column(nullable = false, unique = true)
	@SearchableProperty
	public String getEmail() {
		return email;
	}

	@Temporal(TemporalType.TIMESTAMP)
	@Column(nullable = false, name = "cdate")
	public Date getCreateDate() {
		return createDate;
	}

	public void setCreateDate(Date createDate) {
		this.createDate = createDate;
	}

	@Temporal(TemporalType.TIMESTAMP)
	@Column(nullable = false, name = "mdate")
	public Date getUpdateDate() {
		return updateDate;
	}

	public void setUpdateDate(Date updateDate) {
		this.updateDate = updateDate;
	}

	@Column(nullable = false, name = "activation_code")
	public String getActivationCode() {
		return activationCode;
	}

	public void setActivationCode(String activationCode) {
		this.activationCode = activationCode;
	}

	@Column(nullable = true)
	public String getAvatar() {
		return avatar;
	}

	public void setAvatar(String avatar) {
		this.avatar = avatar;
	}

	@Column(nullable = true, name = "num_friends")
	public Integer getNumberFriends() {
		return numberFriends;
	}

	public void setNumberFriends(Integer numberFriends) {
		this.numberFriends = numberFriends;
	}

	@Column(nullable = true, name = "num_goals")
	public Integer getNumberGoals() {
		return numberGoals;
	}

	public void setNumberGoals(Integer numberGoals) {
		this.numberGoals = numberGoals;
	}

	@Column(nullable = true, name = "num_followed")
	public Integer getNumberFollowed() {
		return numberFollowed;
	}

	public void setNumberFollowed(Integer numberFollowed) {
		this.numberFollowed = numberFollowed;
	}

	@Column(nullable = true, name = "num_follows")
	public Integer getNumberFollows() {
		return numberFollows;
	}

	public void setNumberFollows(Integer numberFollows) {
		this.numberFollows = numberFollows;
	}

//	@ManyToMany(fetch = FetchType.EAGER)
//	@JoinTable(name = "user_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = @JoinColumn(name = "role_id"))
//	public Set<Role> getRoles() {
//		return roles;
//	}
	
	@Transient
	public Set<Role> getRoles(){	
		if(roles == null){
			roles = new HashSet<Role>(0);
			for(UserRole userRole : this.userRoles){
				roles.add(userRole.getRole());
			}
		}
		return roles;
	}

	/**
	 * Convert user roles to LabelValue objects for convenience.
	 * 
	 * @return a list of LabelValue objects with role information
	 */
	@Transient
	public List<LabelValue> getRoleList() {
		List<LabelValue> userRoles = new ArrayList<LabelValue>();

		if (this.roles != null) {
			for (Role role : roles) {
				// convert the user's roles to LabelValue Objects
				userRoles.add(new LabelValue(role.getName(), role.getName()));
			}
		}

		return userRoles;
	}

	/**
	 * Adds a role for the user
	 * 
	 * @param role
	 *            the fully instantiated role
	 */
	public void addRole(Role role) {
		getRoles().add(role);
	}

	/**
	 * @return GrantedAuthority[] an array of roles.
	 * @see org.springframework.security.userdetails.UserDetails#getAuthorities()
	 */
	@Transient
	public GrantedAuthority[] getAuthorities() {
		if(roles == null){
			roles = new HashSet<Role>(0);
			for(UserRole userRole : userRoles){
				roles.add(userRole.getRole());
			}
		}
		return roles.toArray(new GrantedAuthority[0]);
	}

	@Version
	public Integer getVersion() {
		return version;
	}

	@Column(name = "account_enabled")
	@Type(type = "yes_no")
	public boolean isEnabled() {
		return enabled;
	}

	@Column(name = "account_expired", nullable = false)
	@Type(type = "yes_no")
	public boolean isAccountExpired() {
		return accountExpired;
	}

	/**
	 * @see org.springframework.security.userdetails.UserDetails#isAccountNonExpired()
	 */
	@Transient
	public boolean isAccountNonExpired() {
		return !isAccountExpired();
	}

	@Column(name = "account_locked", nullable = false)
	@Type(type = "yes_no")
	public boolean isAccountLocked() {
		return accountLocked;
	}

	/**
	 * @see org.springframework.security.userdetails.UserDetails#isAccountNonLocked()
	 */
	@Transient
	public boolean isAccountNonLocked() {
		return !isAccountLocked();
	}

	@Column(name = "credentials_expired", nullable = false)
	@Type(type = "yes_no")
	public boolean isCredentialsExpired() {
		return credentialsExpired;
	}

	/**
	 * @see org.springframework.security.userdetails.UserDetails#isCredentialsNonExpired()
	 */
	@Transient
	public boolean isCredentialsNonExpired() {
		return !credentialsExpired;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public void setConfirmPassword(String confirmPassword) {
		this.confirmPassword = confirmPassword;
	}

	public void setPasswordHint(String passwordHint) {
		this.passwordHint = passwordHint;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public void setRoles(Set<Role> roles) {
		this.roles = roles;
	}

	public void setVersion(Integer version) {
		this.version = version;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public void setAccountExpired(boolean accountExpired) {
		this.accountExpired = accountExpired;
	}

	public void setAccountLocked(boolean accountLocked) {
		this.accountLocked = accountLocked;
	}

	public void setCredentialsExpired(boolean credentialsExpired) {
		this.credentialsExpired = credentialsExpired;
	}

	@OneToMany(mappedBy = "to", fetch = FetchType.LAZY)
	public Set<UserFriend> getFriends() {
		return friends;
	}

	public void setFriends(Set<UserFriend> friends) {
		this.friends = friends;
	}

	public void addFriend(UserFriend friend) {
		this.getFriends().add(friend);
	}

	@OneToMany(mappedBy = "from", fetch = FetchType.LAZY)
	public Set<UserFriend> getFriendOf() {
		return friendOf;
	}

	public void setFriendOf(Set<UserFriend> friendOf) {
		this.friendOf = friendOf;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.user")
	public Set<UserGoal> getUserGoals() {
		return userGoals;
	}

	public void setUserGoals(Set<UserGoal> userGoals) {
		this.userGoals = userGoals;
	}
	
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.user")
	public Set<UserRole> getUserRoles() {
		return userRoles;
	}

	public void setUserRoles(Set<UserRole> userRoles) {
		this.userRoles = userRoles;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.user")
	public Set<UserCategory> getUserCategories() {
		return userCategories;
	}

	public void setUserCategories(Set<UserCategory> categories) {
		this.userCategories = categories;
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		
		if(o instanceof User) {
			User usr = (User)o;
			if(username != null && username.equals(usr.getUsername())) {
				return true;
			}
		}
		
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	public int hashCode() {
		return username == null? 13: username.hashCode();
	}

	/**
	 * {@inheritDoc}
	 */
	public String toString() {
		ToStringBuilder sb = new ToStringBuilder(this,
				ToStringStyle.DEFAULT_STYLE).append("username", this.username)
				.append("enabled", this.enabled)
				.append("accountExpired", this.accountExpired)
				.append("credentialsExpired", this.credentialsExpired)
				.append("accountLocked", this.accountLocked);

		GrantedAuthority[] auths = this.getAuthorities();
		if (auths != null) {
			sb.append("Granted Authorities: ");

			for (int i = 0; i < auths.length; i++) {
				if (i > 0) {
					sb.append(", ");
				}
				sb.append(auths[i].toString());
			}
		} else {
			sb.append("No Granted Authorities");
		}
		return sb.toString();
	}

	public void setUserInfo(UserInfo userInfo) {
		this.userInfo = userInfo;
	}

	@OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
	@Cascade({ CascadeType.SAVE_UPDATE })
	public UserInfo getUserInfo() {
		return userInfo;
	}

//	@OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
//	@Cascade({ CascadeType.SAVE_UPDATE })
//	public UserStatus getUserStatus() {
//		return userStatus;
//	}
//
//	public void setUserStatus(UserStatus userStatus) {
//		this.userStatus = userStatus;
//	}

	@Transient
	public int getDay() {
		return day;
	}
	
	@OneToMany(mappedBy="user", fetch=FetchType.LAZY)
	public Set<UserStatus> getUserStatuses() {
		return userStatuses;
	}

	public void setUserStatuses(Set<UserStatus> userStatuses) {
		this.userStatuses = userStatuses;
	}
	
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "postOwner")
	public Set<UserThought> getWallPosts() {
		return this.wallPosts;
	}

	public void setWallPosts(Set<UserThought> wallPosts) {
		this.wallPosts = wallPosts;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "wallOwner")
	public Set<UserThought> getPostsOnWall() {
		return this.postsOnWall;
	}
	
	public void setPostsOnWall(Set<UserThought> postsOnWall) {
		this.postsOnWall = postsOnWall;
	}
	
	@OneToMany(mappedBy="componentOwner", fetch=FetchType.LAZY)
	public Set<UserActivity> getNotificationLogs() {
		return notificationLogs;
	}

	public void setNotificationLogs(Set<UserActivity> notificationLogs) {
		this.notificationLogs = notificationLogs;
	}
	
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "actionOwner")
	public Set<UserActivity> getActionLogs() {
		return actionLogs;
	}

	public void setActionLogs(Set<UserActivity> actionLogs) {
		this.actionLogs = actionLogs;
	}

	@OneToMany(mappedBy="invite", fetch=FetchType.LAZY)
	public Set<InfoQueueEntry> getInvites() {
		return invites;
	}

	public void setInvites(Set<InfoQueueEntry> invites) {
		this.invites = invites;
	}

	@OneToMany(mappedBy = "owner", fetch = FetchType.LAZY)
	public Set<Xmas2010> getXmasWishes() {
		return xmasWishes;
	}

	public void setXmasWishes(Set<Xmas2010> xmasWishes) {
		this.xmasWishes = xmasWishes;
	}

	public void addUserStatus(UserStatus userStatus) {
		if (this.userStatuses == null) {
			this.userStatuses = new HashSet<UserStatus>(0);
		}

		this.userStatuses.add(userStatus);
	}
	
	public void addWallPost(UserThought post) {
		if (this.wallPosts == null) {
			this.wallPosts = new HashSet<UserThought>(0);
		}

		this.wallPosts.add(post);
	}
	
	public void addXmasWish(Xmas2010 wish) {
		if (this.xmasWishes == null) {
			this.xmasWishes = new HashSet<Xmas2010>(0);
		}
		
		this.xmasWishes.add(wish);
	}

	public void setDay(int day) {
		this.day = day;
	}

	@Transient
	public int getMonth() {
		return month;
	}

	public void setMonth(int month) {
		this.month = month;
	}

	@Transient
	public int getYear() {
		return year;
	}

	public void setYear(int year) {
		this.year = year;
	}

	@Transient
	public DataStatus getStatus() {
		return DataStatus.USING;
	}

	public void setStatus(DataStatus dataStatus) {
		return;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "to")
	public Set<Message> getReceivedMessages() {
		return this.receivedMessages;
	}

	public void setReceivedMessages(Set<Message> receivedMessages) {
		this.receivedMessages = receivedMessages;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "from")
	public Set<Message> getSentMessages() {
		return this.sentMessages;
	}

	public void setSentMessages(Set<Message> sentMessage) {
		this.sentMessages = sentMessage;
	}
	
	@ManyToMany(fetch = FetchType.LAZY)
	@JoinTable(name = "user_hobby", joinColumns = { @JoinColumn(name = "user_id", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "hobby_id", nullable = false, updatable = false) })
	public Set<Hobby> getHobbies() {
		return this.hobbies;
	}

	public void setHobbies(Set<Hobby> hobbies) {
		this.hobbies = hobbies;
	}
	
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
	public Set<UserJob> getUserJobs() {
		return this.userJobs;
	}

	public void setUserJobs(Set<UserJob> userJobs) {
		this.userJobs = userJobs;
	}
	
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.user")
	public Set<UserSchool> getUserSchools() {
		return this.userSchools;
	}

	public void setUserSchools(Set<UserSchool> userSchools) {
		this.userSchools = userSchools;
	}
	
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.user")
	public Set<UserCompany> getUserCompanies() {
		return userCompanies;
	}

	public void setUserCompanies(Set<UserCompany> userCompanies) {
		this.userCompanies = userCompanies;
	}

	@Transient
	public Set<Goal> getGoals() {
		if(goals == null){
			goals = new HashSet<Goal>(0);
			for(UserGoal userGoal : userGoals){
				goals.add(userGoal.getGoal());
			}
		}
		return goals;
	}

	@Transient
	public Set<Category> getCategories() {
		if(categories == null){
			categories = new HashSet<Category>(0);
			for(UserCategory userCategory : userCategories){
				categories.add(userCategory.getCategory());
			}
		}
		return categories;
	}

	@Column(name="subscription_type", nullable=true)
	public Integer getSubscriptionType() {
		return subscriptionType;
	}

	public void setSubscriptionType(Integer subscriptionType) {
		this.subscriptionType = subscriptionType;
	}

	@Type(type = "yes_no")
	@Column(name="receive_subscription", nullable=true)
	public boolean isReceiveSubscription() {
		return receiveSubscription;
	}

	public void setReceiveSubscription(boolean receiveSubscription) {
		this.receiveSubscription = receiveSubscription;
	}

}
